/*
 * Alex Rodrigo de Oliveira - 06/30403
 * Rafaela Porto Osório - 06/94011
 */

#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <assert.h>
#include "sig_handlers.h"
#include "list.h"
#include "debug.h"


/*
 * Estrutura vazia para passar para uma função de tratamento de sinal, simulando
 * a ocorrência de um sinal.
 */
static siginfo_t NOTHING;


/*
 * Funções de tratamento dos sinais.
 */
static void hang_up(int signum, siginfo_t *info, void *);
static void exec_proc(int signum, siginfo_t *info, void *);
static void wait_child(int signum, siginfo_t *info, void *);


/*
 * Estruturas que determinam o tratamento dos sinais.
 * Colocadas aqui para que já comecem com todos os campos zerados.
 */
static struct sigaction wait_child_t;
static struct sigaction exec_proc_t;
static struct sigaction hang_up_t;


/*
 * Escolhe as funções que irão tratar os sinais que este processo está preparado
 * para receber.
 */
void init_sig_handlers()
{
	/* Escolhe o handler para SIGCHLD. */
	wait_child_t.sa_sigaction = wait_child;
	wait_child_t.sa_flags = SA_SIGINFO;
	sigaction(SIGCHLD, &wait_child_t, NULL);

	/* Escolhe o handler para SIGALRM. */
	exec_proc_t.sa_sigaction = exec_proc;
	exec_proc_t.sa_flags = SA_SIGINFO;
	sigaction(SIGALRM, &exec_proc_t, NULL);

	/* Escolhe o handler para SIGINT e SIGTERM. */
	hang_up_t.sa_sigaction = hang_up;
	hang_up_t.sa_flags = SA_SIGINFO;
	sigaction(SIGTERM, &hang_up_t, NULL);
	sigaction(SIGINT, &hang_up_t, NULL);
}


/*
 * Função para tratar o sinal SIGCHLD.
 * Deixa o filho morrer, retira ele da lista running e imprime informações.
 */
void wait_child(int signum, siginfo_t *info, void *none)
{
	assert(signum == SIGCHLD);
	assert(info->si_pid > 0);

	const time_t NOW = time(NULL);
	const struct tm AFTER = *localtime(&NOW);
	struct tm before;
	int cpid;
	Node *nd;
	Job child;

	/* Deixa o filho morrer, para não virar zumbi. */
	wait(NULL);

	/* Procura o filho que acabou de morrer na lista de processos em execução
	   (pelo pid), copia-o para a variável child e remove-o dessa lista. */
	cpid = info->si_pid;
	for (nd = running.begin; nd != running.end; nd = nd->next) {
		if (nd->data.pid == cpid) break;
	}
	assert(nd != running.end);
	child = nd->data;
	list_rem(&running, nd);

	/* Imprime as informações sobre o processo que terminou a execução. */
	before = *localtime(&child.time);
	printf("Job %d (%s) havia iniciado às %d:%d:%d e terminou às %d:%d:%d\n",
			child.id, child.command,
			before.tm_hour, before.tm_min, before.tm_sec,
			AFTER.tm_hour, AFTER.tm_min, AFTER.tm_sec);
}


/*
 * Função para tratar o SIGALRM.
 * Faz um fork, o filho vai executar um novo código e o pai vai ajustar o alarme
 * e as listas schedule e running.
 */
void exec_proc(int signum, siginfo_t *info, void *none)
{
	assert(signum == SIGALRM);
	assert(schedule.begin != schedule.end);
	assert(schedule.begin->data.time <= time(NULL));

	int cpid, delay;
	Node *nd;
	Job now = schedule.begin->data;

	cpid = fork();

	/* Pai. */
	if (cpid) {

		/* Remove a cabeça da lista, que é o job que será executado agora. Esse
		   job já foi copiado para a variável now. */
		list_rem(&schedule, schedule.begin);

		/* Anota o pid do processo em now para encontrá-lo na lista running
		   quando ele terminar. Insere uma cópia do now na lista running. */
		now.pid = cpid;
		list_add(&running, running.end, &now);

		/* Decrementa o contador de execuções. Se chegou a zero, pode descartar
		   o now. */
		--now.repeats;
		if (now.repeats > 0) {

			/* Se precisar executar o job de novo, recalcula o instante da
			   próxima execução e o insere ordenado na lista. */
			now.time += now.delay;
			for (nd = schedule.begin; nd != schedule.end; nd = nd->next) {
				if (now.time < nd->data.time) break;
			}
			list_add(&schedule, nd, &now);

		}

		/* Se ainda tiver algum processo agendado, recalcula o tempo até o
		   próximo alarme. Mas se for zero, já chama a função recursivamente. */
		if (schedule.len == 0) return;
		delay = schedule.begin->data.time - time(NULL);
		if (delay <= 0) {
			exec_proc(SIGALRM, &NOTHING, NULL);
		} else {
			alarm(delay);
		}
	}

	/* Filho. */
	else {
		debug_print("O processo filho irá executar com o comando: %s\n",
				now.command);
		execl(now.command, now.command, NULL);
		// OBS: A especificação não pede para aceitar parâmetros para o novo programa
	}
}


/*
 * Função para tratar o SIGINT e o SIGTERM.
 * Não precisa fazer nada, pois já temos uma função de arrumação da casa.
 */
void hang_up(int signum, siginfo_t *info, void *none)
{
	exit(0);
}
